package com.ttg.utils.connection;

//~--- non-JDK imports -------------
import com.ttg.utils.connection.handler.BytesResponseHandler;
import com.ttg.utils.connection.handler.FileResponseHandler;
import com.ttg.utils.connection.handler.StringResponseHandler;
import org.apache.http.NameValuePair;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//~--- JDK imports ------------------------------------------------------------

//~--- classes ----------------------------------------------------------------

/**
 * Class HttpClient
 * Description
 * Create 2016-02-25 01:15:07 
 * @author Ardy    
 */
public class HttpClient {

    /** 
     * Field lock
     * Description 
     */
    private static Lock lock = new ReentrantLock();

    /** 
     * Field defaultHttpConnectionManagerTimeout
     * Description 
     */
    private static final long defaultHttpConnectionManagerTimeout = 120000L;

    /** 
     * Field httpClient
     * Description 
     */
    private static HttpClient httpClient;

    /** 
     * Field logger
     * Description 
     */
    private final Logger logger = LoggerFactory.getLogger(HttpClient.class);

    /** 
     * Field defaultConnectionTimeout
     * Description 
     */
    private final int defaultConnectionTimeout = 60000;

    /** 
     * Field defaultSoTimeout
     * Description 
     */
    private final int defaultSoTimeout = 300000;

    /** 
     * Field defaultIdleConnTimeout
     * Description 
     */
    private final int defaultIdleConnTimeout = '\uea60';

    /** 
     * Field defaultMaxPerRoute
     * Description 
     */
    private final int defaultMaxPerRoute = 30;

    /** 
     * Field defaultMaxTotalConn
     * Description 
     */
    private final int defaultMaxTotalConn = 80;

    /** 
     * Field connectionManager
     * Description 
     */
    private final PoolingHttpClientConnectionManager connectionManager;

    /** 
     * Field ict
     * Description 
     */
    private final IdleConnectionMonitorThread ict;

    /**
     * Constructs HttpClient
     * Description
     *
     * @throws TTGHttpClientException 异常
     */
    private HttpClient() throws TTGHttpClientException {
        try {

            // SSLContext t = SSLContexts.getInstance ("TLS");
            // SSLContext sslContext = SSLContexts.createDefault ();
            SSLContext       sslContext = SSLContexts.custom().build();
            X509TrustManager tm         = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
                public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
            };

            sslContext.init(null, new TrustManager[] { tm }, null);

            SSLConnectionSocketFactory ssf                   = new SSLConnectionSocketFactory(sslContext);
            Registry<ConnectionSocketFactory> socketFactoryRegistry =
                RegistryBuilder.<ConnectionSocketFactory>create().register("http",
                                 PlainConnectionSocketFactory.INSTANCE).register("https", ssf).build();

/*            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme ("http", 80, PlainSocketFactory.getSocketFactory ()));
            registry.register(new Scheme("https", 443, ssf));*/
            this.connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            this.connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
            this.connectionManager.setMaxTotal(defaultMaxTotalConn);
            this.ict = new IdleConnectionMonitorThread(this.connectionManager, defaultConnectionTimeout);
            this.ict.start();
        } catch (Throwable var6) {
            throw new TTGHttpClientException(var6);
        }

        try {
            Runtime.getRuntime().addShutdownHook(new ContainerShutdownHook(httpClient));
        } catch (Throwable var5) {}
    }

    /**
     * Method get 
     * Description 说明：
     *
     * @return 返回值说明：
     *
     * @throws TTGHttpClientException 异常：
     */
    public static HttpClient get() throws TTGHttpClientException {
        if (httpClient == null) {
            lock.lock();

            try {
                if (httpClient == null) {
                    httpClient = new HttpClient();
                }
            } finally {
                lock.unlock();
            }
        }

        return httpClient;
    }

    /**
     * Method requestWithStringResult 
     * Description 说明：
     *
     * @param request 说明：
     * @param retryCount 说明：
     * @param requestSentRetryEnabled 说明：
     *
     * @return 返回值说明：
     *
     * @throws TTGHttpClientException 异常：
     */
    public String requestWithStringResult(HttpRequest request, int retryCount, boolean requestSentRetryEnabled)
            throws TTGHttpClientException {
        return this.requestWithCustomObjectResult(request, retryCount, requestSentRetryEnabled,
                new StringResponseHandler());
    }


    /**
     * Method requestWithBytesResult 
     * Description 说明：
     *
     * @param request 说明：
     * @param retryCount 说明：
     * @param requestSentRetryEnabled 说明：
     *
     * @return 返回值说明：
     *
     * @throws TTGHttpClientException 异常：
     */
    public byte[] requestWithBytesResult(HttpRequest request, int retryCount, boolean requestSentRetryEnabled)
            throws TTGHttpClientException {
        return this.requestWithCustomObjectResult(request, retryCount, requestSentRetryEnabled,
                new BytesResponseHandler());
    }

    /**
     * Method requestStrWithStringResult 
     * Description 说明：
     *
     * @param request 说明：
     * @param retyrCount 说明：
     * @param requestSentRetryEnabled 说明：
     *
     * @return 返回值说明：
     *
     * @throws TTGHttpClientException 异常：
     */
    public String requestStrWithStringResult(HttpRequest request, int retyrCount, boolean requestSentRetryEnabled)
            throws TTGHttpClientException {
        return this.requestStringWithCustomObjectResult(request, retyrCount, requestSentRetryEnabled,
                new StringResponseHandler());
    }

    /**
     * Method requestWithFileResult 
     * Description 说明：
     *
     * @param request 说明：
     * @param downloadFile 说明：
     * @param retryCount 说明：
     * @param requestSentRetryEnabled 说明：
     *
     * @return 返回值说明：
     *
     * @throws TTGHttpClientException 异常：
     */
    public File requestWithFileResult(HttpRequest request, File downloadFile, int retryCount,
                                      boolean requestSentRetryEnabled)
            throws TTGHttpClientException {
        return this.requestWithCustomObjectResult(request, retryCount, requestSentRetryEnabled,
                new FileResponseHandler(downloadFile));
    }

    /**
     * Method requestWithCustomObjectResult 
     * Description 说明：
     *
     * @param request 说明：
     * @param retryCount 说明：
     * @param requestSentRetryEnabled 说明：
     * @param responseHandler 说明：
     * @param <T> 说明：
     *
     * @return 返回值说明：
     *
     * @throws TTGHttpClientException 异常：
     */
    public <T> T requestWithCustomObjectResult(HttpRequest request, int retryCount, boolean requestSentRetryEnabled,
                                               ResponseHandler<T> responseHandler)
            throws TTGHttpClientException {
        CloseableHttpClient httpclient = null;
        HttpRequestBase method     = null;
        T                   var8;

        try {
            httpclient = this.initHttpClient(request, retryCount, requestSentRetryEnabled);
            method     = this.initHttpMethod(request);
            logger.debug("开始发送http请求:targetUrl[{}],parameters[{}]", new Object[] { request.getTargetUrl(),
                    request.getParameters() });

            T ex = httpclient.execute(method, responseHandler);

            var8 = ex;
        } catch (Throwable var16) {
            throw new TTGHttpClientException(var16);
        } finally {
            try {
                if (method != null) {
                    method.releaseConnection();
                }
            } catch (Throwable var17) {
                throw new TTGHttpClientException(var17);
            }
        }

        return var8;
    }


    /**
     * Method requestStringWithCustomObjectResult 
     * Description 说明：
     *
     * @param request 说明：
     * @param retryCount 说明：
     * @param requestSentRetryEnabled 说明：
     * @param responseHandler 说明：
     * @param <T> 说明：
     *
     * @return 返回值说明：
     *
     * @throws TTGHttpClientException 异常：
     */
    public <T> T requestStringWithCustomObjectResult(HttpRequest request, int retryCount,
                                                     boolean requestSentRetryEnabled, ResponseHandler<T> responseHandler)
            throws TTGHttpClientException {
        CloseableHttpClient httpclient = null;
        HttpRequestBase method     = null;
        T                   var8;

        try {
            httpclient = this.initHttpClient(request, retryCount, requestSentRetryEnabled);
            method     = this.initStringPost(request);
            logger.debug("开始发送http请求:targetUrl[{}],parameters[{}]", new Object[] { request.getTargetUrl(),
                    request.getParameters() });

            T ex = httpclient.execute(method, responseHandler);

            logger.debug("响应的数据：[{}]", ex);
            var8 = ex;
        } catch (Throwable var16) {
            throw new TTGHttpClientException(var16);
        } finally {
            try {
                if (method != null) {
                    method.releaseConnection();
                }
            } catch (Throwable var17) {
                throw new TTGHttpClientException(var17);
            }
        }

        return var8;
    }

    /**
     * Method initHttpClient 
     * Description 说明：
     *
     * @param request 说明：
     * @param retryCount 说明：
     * @param requestSentRetryEnabled 说明：
     *
     * @return 返回值说明：
     */
    private CloseableHttpClient initHttpClient(HttpRequest request, int retryCount, boolean requestSentRetryEnabled) {
        int connectionTimeout = 30000;

        if (request.getConnectionTimeout() > 0) {
            connectionTimeout = request.getConnectionTimeout();
        }

        int soTimeout = 300000;

        if (request.getTimeout() > 0) {
            soTimeout = request.getTimeout();
        }

        ConnectionConfig connectionConfig =
            ConnectionConfig.custom().setCharset(Charset.forName(request.getCharset())).build();

        this.connectionManager.setDefaultConnectionConfig(connectionConfig);

        RequestConfig config = RequestConfig.custom().setCookieSpec(
                                   CookieSpecs.STANDARD_STRICT).setConnectionRequestTimeout(
                                   connectionTimeout).setSocketTimeout(soTimeout).setConnectTimeout(
                                   (int) defaultHttpConnectionManagerTimeout).setRedirectsEnabled(
                                   true).setRelativeRedirectsAllowed(true).build();
        CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(
                                             this.connectionManager).setRetryHandler(
                                             new DefaultHttpRequestRetryHandler(retryCount,
                                                     requestSentRetryEnabled)).setDefaultRequestConfig(config).build();

        return httpclient;
    }

    /**
     * Method initHttpMethod 
     * Description 说明：
     *
     * @param request 说明：
     *
     * @return 返回值说明：
     *
     * @throws URISyntaxException 异常：
     * @throws UnsupportedEncodingException 异常：
     */
    private HttpRequestBase initHttpMethod(HttpRequest request)
            throws URISyntaxException, UnsupportedEncodingException {
        HttpRequestBase method = null;

        // RequestConfig config = RequestConfig.custom().setExpectContinueEnabled(false).build();
        if (request.getMethod().equals("GET")) {
            method = this.initGet(request);
        } else {
            method = this.initPost(request);
        }

        method.addHeader("User-Agent", "Mozilla/4.0");
        method.addHeader("Connection", "close");
        method.removeHeaders("Expect");
        method.addHeader("http.auth.credential-charset", request.getCharset());

        return method;
    }

    /**
     * Method initGet 
     * Description 说明：
     *
     * @param request 说明：
     *
     * @return 返回值说明：
     *
     * @throws URISyntaxException 异常：
     */
    private HttpRequestBase initGet(HttpRequest request) throws URISyntaxException {
        URIBuilder builder    = new URIBuilder(request.getTargetUrl());
        Map        parameters = request.getParameters();
        Iterator   method     = parameters.keySet().iterator();

        while (method.hasNext()) {
            String key = (String) method.next();

            builder.addParameter(key, (String) parameters.get(key));
        }

        HttpGet method1 = new HttpGet(builder.build());

        return method1;
    }


    /**
     * Method initStringPost 
     * Description 说明：
     *
     * @param request 说明：
     *
     * @return 返回值说明：
     */
    private HttpRequestBase initStringPost(HttpRequest request) {
        HttpPost method = new HttpPost(request.getTargetUrl());

        // 将要提交给API的数据对象转换成XML格式数据Post给API
        String postData = String.valueOf(request.getParameters().get("data"));

        logger.debug("请求的报文：{}", postData);

        // 得指明使用UTF-8编码，否则到API服务器XML的中文不能被成功识别
        StringEntity postEntity = new StringEntity(postData, "UTF-8");

        method.addHeader("Content-Type", "application/json");
        method.setEntity(postEntity);

        return method;
    }

    /**
     * Method initPost 
     * Description 说明：
     *
     * @param request 说明：
     *
     * @return 返回值说明：
     *
     * @throws UnsupportedEncodingException 异常：
     */
    private HttpRequestBase initPost(HttpRequest request) throws UnsupportedEncodingException {
        HttpPost method     = new HttpPost(request.getTargetUrl());
        Map      parameters = request.getParameters();
        Iterator urlEncodedFormEntity;
        String   key;

        if (request instanceof UploadHttpRequest) {
            MultipartEntityBuilder nvps = MultipartEntityBuilder.create();

            urlEncodedFormEntity = parameters.keySet().iterator();

            while (urlEncodedFormEntity.hasNext()) {
                key = (String) urlEncodedFormEntity.next();
                nvps.addPart(key, new StringBody((String) parameters.get(key),
                                                 "text/plain",
                                                 Charset.forName(request.getCharset())));
            }

            urlEncodedFormEntity = ( (UploadHttpRequest) request ).getFiles().iterator();

            while (urlEncodedFormEntity.hasNext()) {
                HttpUploadFile key1     = (HttpUploadFile) urlEncodedFormEntity.next();
                String         fileName = key1.getFileName();

                fileName = URLEncoder.encode(fileName, request.getCharset());

                if (key1.getFile() != null) {
                    nvps.addPart(key1.getParameterName(), new FileBody(key1.getFile(),
                            fileName,
                            "application/octet-stream",
                            request.getCharset()));
                } else {
                    nvps.addPart(key1.getParameterName(), new ByteArrayBody(key1.getContent(),
                            "application/octet-stream",
                            fileName));
                }
            }

            method.setEntity(nvps.build());
        } else {
            parameters = ( null == parameters ) ? new HashMap<>() : parameters;

            ArrayList nvps1 = new ArrayList(parameters.size());

            urlEncodedFormEntity = parameters.keySet().iterator();

            while (urlEncodedFormEntity.hasNext()) {
                key = (String) urlEncodedFormEntity.next();
                nvps1.add(new BasicNameValuePair(key, (String) parameters.get(key)));
            }

            UrlEncodedFormEntity urlEncodedFormEntity1 = new UrlEncodedFormEntity(nvps1, request.getCharset());

            method.setEntity(urlEncodedFormEntity1);
            method.addHeader("Content-Type",
                             "application/x-www-form-urlencoded; text/html; charset=" + request.getCharset());
        }

        return method;
    }

    /**
     * Method toString 
     * Description 说明：
     *
     * @param nameValues 说明：
     *
     * @return 返回值说明：
     */
    protected String toString(NameValuePair[] nameValues) {
        if (( nameValues != null ) && ( nameValues.length != 0 )) {
            StringBuffer buffer = new StringBuffer();

            for (int i = 0 ; i < nameValues.length ; ++i) {
                NameValuePair nameValue = nameValues[i];

                if (i == 0) {
                    buffer.append(nameValue.getName() + "=" + nameValue.getValue());
                } else {
                    buffer.append("&" + nameValue.getName() + "=" + nameValue.getValue());
                }
            }

            return buffer.toString();
        } else {
            return "null";
        }
    }

    /**
     * Method close 
     * Description 说明：
     */
    public void close() {
        this.ict.shutdown();
        this.connectionManager.shutdown();
        logger.info("Close httpClient completed");
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
